import { Injectable, Injector } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable, throwError } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { NzNotificationService, NzMessageService } from 'ng-zorro-antd'

import { COMB_URL } from 'app/utils/contraints'
import { StoreService } from './store.service'
import { preProcessParams, encodeParams } from '@shared/decorators'
import {
  IStorePagination,
  IStoreQueryParams,
  StoreCardFeatureUpdate,
  StoreCreate,
  StoreFull,
  StoreQRFeatureUpdate,
  StoreRiskControlUpdate,
  StoreSettlementUpdate,
  StoreUpdate,
  IQRRouteOfStore,
  IStoreReportPagination,
  ICardRouteOfStore,
  IECRouteOfStore,
} from '@shared/interfaces/store'
import { QrChannel, ExchangeRate, ICardChannel, IECUPIChannel } from '@shared/interfaces/acquirer'
import { UserRole, IAllLevelRoles } from '@shared/interfaces/user'
import { IGeneralResponse } from '@shared/interfaces/response'
import { I18NService } from '@core'
import { AcquirerService } from '../acquirer/acquirer.service'

@Injectable()
export class StoreAlinterService implements StoreService {
  constructor(private injector: Injector, private http: HttpClient, private acquirerService: AcquirerService) {}

  private get notification(): NzNotificationService {
    return this.injector.get(NzNotificationService)
  }

  private get msgSrv(): NzMessageService {
    return this.injector.get(NzMessageService)
  }

  private get i18n(): I18NService {
    return this.injector.get(I18NService)
  }

  getStoreRoles(insCode: string): Observable<UserRole[]> {
    const url = `${COMB_URL}/camel/institution/${insCode}/roles`
    return this.http
      .get<IAllLevelRoles>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body.storeRoles
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  deleteQRRoute(intStoreCode: string, id: string): Observable<void> {
    const url = `${COMB_URL}/zebra/store/${intStoreCode}/channel/${id}`
    return this.http
      .delete<void>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  updateQRRoute(intStoreCode: string, id: string, route: IQRRouteOfStore): Observable<IQRRouteOfStore> {
    const url = `${COMB_URL}/zebra/store/${intStoreCode}/channel/${id}`
    return this.http.put<IQRRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  createQRRoute(intStoreCode: string, route: IQRRouteOfStore): Observable<IQRRouteOfStore> {
    const url = `${COMB_URL}/zebra/store/${intStoreCode}/channel`
    return this.http.post<IQRRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  getQRRoute(intStoreCode: string, id: string): Observable<IQRRouteOfStore> {
    const url = `${COMB_URL}/zebra/store/${intStoreCode}/channel/${id}`
    return this.http
      .get<IQRRouteOfStore>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  getAcquirerQRChannels(insCode: string): Observable<QrChannel[]> {
    const url = `${COMB_URL}/zebra/institution/${insCode}/channels`
    return this.http
      .get<QrChannel[]>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  getCardRoute(intStoreCode: string, id: string): Observable<ICardRouteOfStore> {
    const url = `${COMB_URL}/turtle/store/${intStoreCode}/channel/${id}`
    return this.http
      .get<ICardRouteOfStore>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  updateCardRoute(intStoreCode: string, id: string, route: ICardRouteOfStore): Observable<ICardRouteOfStore> {
    const url = `${COMB_URL}/turtle/store/${intStoreCode}/channel/${id}`
    return this.http.put<ICardRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  createCardRoute(intStoreCode: string, route: ICardRouteOfStore): Observable<ICardRouteOfStore> {
    const url = `${COMB_URL}/turtle/store/${intStoreCode}/channel`
    return this.http.post<ICardRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  deleteCardRoute(intStoreCode: string, id: string): Observable<void> {
    const url = `${COMB_URL}/turtle/store/${intStoreCode}/channel/${id}`
    return this.http
      .delete<void>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  getAcquirerCardChannels(insCode: string): Observable<ICardChannel[]> {
    const url = `${COMB_URL}/turtle/institution/${insCode}/channels`
    return this.http
      .get<ICardChannel[]>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

getAcquirerECChannels(insCode: string): Observable<IECUPIChannel[]> {
    const url = `${COMB_URL}/eagle/institution/${insCode}/channels`
    return this.http
      .get<IECUPIChannel[]>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  getECRoute(intStoreCode: string, id: string): Observable<IECRouteOfStore> {
    const url = `${COMB_URL}/eagle/store/${intStoreCode}/channel/${id}`
    return this.http
      .get<IECRouteOfStore>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  updateECRoute(intStoreCode: string, id: string, route: IECRouteOfStore): Observable<IECRouteOfStore> {
    const url = `${COMB_URL}/eagle/store/${intStoreCode}/channel/${id}`
    return this.http.put<IECRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  createECRoute(intStoreCode: string, route: IECRouteOfStore): Observable<IECRouteOfStore> {
    const url = `${COMB_URL}/eagle/store/${intStoreCode}/channel`
    return this.http.post<IECRouteOfStore>(url, route).pipe(catchError(err => throwError(err)))
  }

  deleteECRoute(intStoreCode: string, id: string): Observable<void> {
    const url = `${COMB_URL}/eagle/store/${intStoreCode}/channel/${id}`
    return this.http
      .delete<void>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  updateCardFeature(feature: StoreCardFeatureUpdate): Observable<StoreFull> {
    throw new Error('Method not implemented.')
  }

  @preProcessParams()
  @encodeParams()
  public getList(params: IStoreQueryParams, str?: string): Observable<IStorePagination> {
    const url = `${COMB_URL}/camel/stores?${str}`
    return this.http
      .get<IStorePagination>(url, {
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          return throwError(err)
        }),
      )
  }

  public getOne(intStoreCode: string): Observable<StoreFull> {
    const url = `${COMB_URL}/camel/store/${intStoreCode}/full`
    return this.http
      .get<StoreFull>(url, {
        withCredentials: true,
        observe: 'response',
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            const full = response.body
            full.qrChannels = full.qrChannels || []
            full.cardChannels = full.cardChannels || []
            full.ecPayChannels = full.ecPayChannels || []
            return full
          }

          throwError(response)
        }),
        catchError(err => throwError(err)),
      )
  }

  public create(store: StoreCreate): Observable<StoreFull> {
    const url = `${COMB_URL}/camel/store`
    return this.http
      .post<any>(url, store, { observe: 'response' })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.msgSrv.success(this.i18n.fanyi('GENERAL.NOTIFICATION.CREATE_SUCCESS'))
            return response.body
          }
          if (status === 202) {
            this.msgSrv.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SUBMIT_TO_AUDIT_SUCCESS'))
            return response.body
          }
          throwError(response)
        }),
        catchError(err => throwError(err))
      )
  }

  public update(intStoreCode: string, token: string = '', data: StoreUpdate): Observable < IGeneralResponse | StoreFull > {
    const url = `${COMB_URL}/camel/store/${intStoreCode}`
    return this.http
      .put<IGeneralResponse | StoreFull>(url, data, {
        observe: 'response',
        headers: {
          'X-CIL-Audit-Token': token,
        },
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.UPDATE_SUCCESS'), '')
            return Object.assign(new StoreFull(), response.body)
          }

          if (status === 202) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SAVE_SUCCESS'), '')
            return response.body as IGeneralResponse
          }

          throwError(response)
        }),
        catchError(err => throwError(err)),
      )
  }

  // update settlement information of store
updateSettlement(
    intStoreCode: string,
    token: string = '',
    settlement: StoreSettlementUpdate,
  ): Observable < IGeneralResponse | StoreFull > {
    const url = `${COMB_URL}/camel/store/${intStoreCode}/settlement`
    return this.http
      .put<any>(url, settlement, {
        observe: 'response',
        headers: {
          'X-CIL-Audit-Token': token,
        },
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.UPDATE_SUCCESS'), '')
            return Object.assign(new StoreFull(), response.body)
          }

          if (status === 202) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SUBMIT_TO_AUDIT_SUCCESS'), '')
            return response.body as IGeneralResponse
          }

          throwError(response)
        }),
        catchError(err => {
          // TODO: display error according to the http response code
          return throwError(err)
        }),
      )
  }

updateQRFeature(
    intStoreCode: string,
    token: string = '',
    feature: StoreQRFeatureUpdate,
  ): Observable < IGeneralResponse | StoreFull > {
    const url = `${COMB_URL}/zebra/store/${intStoreCode}/feature`
    return this.http
      .put<any>(url, feature, {
        observe: 'response',
        headers: {
          'X-CIL-Audit-Token': token,
        },
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.UPDATE_SUCCESS'), '')
            return Object.assign(new StoreFull(), response.body)
          }

          if (status === 202) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SUBMIT_TO_AUDIT_SUCCESS'), '')
            return response.body as IGeneralResponse
          }

          throwError(response)
        }),
        catchError(err => {
          // TODO: display error according to the http response code
          return throwError(err)
        }),
      )
  }

updateRiskControl(
    intStoreCode: string,
    token: string = '',
    riskControl: StoreRiskControlUpdate,
  ): Observable < IGeneralResponse | StoreFull > {
    const url = `${COMB_URL}/camel/store/${intStoreCode}/riskcontrol`
    return this.http
      .put<any>(url, riskControl, {
        observe: 'response',
        headers: {
          'X-CIL-Audit-Token': token,
        },
      })
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.UPDATE_SUCCESS'), '')
            return Object.assign(new StoreFull(), response.body)
          }

          if (status === 202) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SUBMIT_TO_AUDIT_SUCCESS'), '')
            return response.body as IGeneralResponse
          }

          throwError(response)
        }),
        catchError(err => {
          // TODO: display error according to the http response code
          return throwError(err)
        }),
      )
  }

getAcquirerExchangeRates(insCode: string): Observable < ExchangeRate[] > {
    const url = `${COMB_URL}/camel/institution/${insCode}/exchangeRate`
    return this.http.get<ExchangeRate[]>(url, {})
  }

  /** 导出门店列表 */
@preProcessParams()
  @encodeParams()
getStoreReport(params: IStoreQueryParams, str ?: string): Observable < IStoreReportPagination > {
    const url = `${COMB_URL}/camel/stores/report?${str}`
    return this.http.get<IStoreReportPagination>(url)
  }

  /** 冻结 */
freeze(intStoreCode: string): Observable < boolean > {
    const url = `${COMB_URL}/camel/store/${intStoreCode}/freeze`
    return this.http.put<boolean>(url, {})
  }

  /** 解冻 */
unfreeze(intStoreCode: string): Observable < boolean > {
    const url = `${COMB_URL}/camel/store/${intStoreCode}/unfreeze`
    return this.http.put<boolean>(url, {})
  }

submit(token: string): Observable < IGeneralResponse > {
    const url = `${COMB_URL}/camel/audit/submission/${token}`
    return this.http
      .post<IGeneralResponse>(
        url,
        {},
        {
          observe: 'response',
        },
      )
      .pipe(
        map(response => {
          const { status } = response
          if (status === 200) {
            this.notification.success(this.i18n.fanyi('GENERAL.NOTIFICATION.SUBMIT_TO_AUDIT_SUCCESS'), '')
            return response.body
          }

          if (status === 208) {
            this.notification.error(`已在审核流程中`, '')
            return response.body
          }

          throwError(response)
        }),
        catchError(err => {
          // TODO: display error according to the http response code
          return throwError(err)
        }),
      )
  }

getAcquirerDualMessageSwitch(insCode: string): Observable < boolean > {
    return this.acquirerService.getOne(insCode).pipe(
      map(acquirer => !!acquirer.institution.common.feature.isSupportDualMessage)
    )
  }
}
